/*
 * $Id: stdio.c,v 1.31 2006-11-18 15:19:40 bacon Exp $
 */

#include <xp/bas/stdio.h>
#include <xp/bas/stdarg.h>
#include <xp/bas/string.h>
#include <xp/bas/memory.h>
#include <xp/bas/ctype.h>
#include <xp/bas/assert.h>
#include <xp/bas/sysapi.h>
#include <xp/bas/locale.h>
#include <xp/bas/str.h>

/* TODO: own implementation of stdio functions */

static xp_char_t* __adjust_format (const xp_char_t* format);

XP_FILE* xp_fopen (const xp_char_t* path, const xp_char_t* mode)
{
#ifdef XP_CHAR_IS_MCHAR
	return fopen (path, mode);
#else
	xp_mchar_t path_mb[XP_PATH_MAX + 1];
	xp_mchar_t mode_mb[10];
	xp_size_t n;

	if (xp_wcstomcs_strict (path, 
		path_mb, xp_countof(path_mb)) == -1) return XP_NULL;

	n = xp_wcstomcs (mode, mode_mb, xp_countof(mode_mb));
	if (n == (xp_size_t)-1 || n < xp_strlen(mode)) return XP_NULL;

	return fopen (path_mb, mode_mb);
#endif
}

int xp_printf (const xp_char_t* fmt, ...)
{
	int n;
	xp_va_list ap;

	xp_va_start (ap, fmt);
	n = xp_vprintf (fmt, ap);
	xp_va_end (ap);
	return n;
}

int xp_fprintf (XP_FILE* file, const xp_char_t* fmt, ...)
{
	int n;
	xp_va_list ap;

	xp_va_start (ap, fmt);
	n = xp_vfprintf (file, fmt, ap);
	xp_va_end (ap);
	return n;
}

int xp_vprintf (const xp_char_t* fmt, xp_va_list ap)
{
	return xp_vfprintf (xp_stdout, fmt, ap);
}

int xp_vfprintf (XP_FILE *stream, const xp_char_t* fmt, xp_va_list ap)
{
	int n;
	xp_char_t* nf = __adjust_format (fmt);
	if (nf == XP_NULL) return -1;

#ifdef XP_CHAR_IS_MCHAR
	n = vfprintf (stream, nf, ap);
#else
	n =  vfwprintf (stream, nf, ap);
#endif
	xp_free (nf);
	return n;
}

int xp_sprintf (xp_char_t* buf, xp_size_t size, const xp_char_t* fmt, ...)
{
	int n;
	xp_va_list ap;

	xp_va_start (ap, fmt);
	n = xp_vsprintf (buf, size, fmt, ap);
	xp_va_end (ap);
	return n;
}

int xp_vsprintf (xp_char_t* buf, xp_size_t size, const xp_char_t* fmt, xp_va_list ap)
{
	int n;
	xp_char_t* nf = __adjust_format (fmt);
	if (nf == XP_NULL) return -1;

#if defined(XP_CHAR_IS_MCHAR)
	n = vsnprintf (buf, size, nf, ap);
#elif defined(_WIN32)
	n = _vsnwprintf (buf, size, nf, ap);
#else
	n = vswprintf (buf, size, nf, ap);
#endif
	if (n < 0 || (xp_size_t)n >= size)
	{
		if (size > 0) buf[size-1] = XP_T('\0');
		n = -1;
	}

	xp_free (nf);
	return n;
}

#define MOD_SHORT       1
#define MOD_LONG        2
#define MOD_LONGLONG    3

#define ADDC(str,c) \
	do { \
		if (xp_str_ccat(&str, c) == (xp_size_t)-1) { \
			xp_str_close (&str); \
			return XP_NULL; \
		} \
	} while (0)

static xp_char_t* __adjust_format (const xp_char_t* format)
{
	const xp_char_t* fp = format;
	int modifier;
	xp_str_t str;
	xp_char_t ch;

	if (xp_str_open (&str, 256) == XP_NULL) return XP_NULL;

	while (*fp != XP_CHAR('\0')) 
	{
		while (*fp != XP_CHAR('\0') && *fp != XP_CHAR('%')) 
		{
			ADDC (str, *fp++);
		}

		if (*fp == XP_CHAR('\0')) break;
		xp_assert (*fp == XP_CHAR('%'));

		ch = *fp++;	
		ADDC (str, ch); /* add % */

		ch = *fp++;

		/* flags */
		while (1)
		{
			if (ch == XP_CHAR(' ') || ch == XP_CHAR('+') ||
			    ch == XP_CHAR('-') || ch == XP_CHAR('#')) 
			{
				ADDC (str, ch);
				ch = *fp++;
			}
			else 
			{
				if (ch == XP_CHAR('0')) 
				{
					ADDC (str, ch);
					ch = *fp++; 
				}

				break;
			}
		}

		/* check the width */
		if (ch == XP_CHAR('*')) 
		{
			ADDC (str, ch);
			ch = *fp++;
		}
		else 
		{
			while (xp_isdigit(ch)) 
			{
				ADDC (str, ch);
				ch = *fp++;
			}
		}

		/* precision */
		if (ch == XP_CHAR('.')) 
		{
			ADDC (str, ch);
			ch = *fp++;

			if (ch == XP_CHAR('*')) 
			{
				ADDC (str, ch);
				ch = *fp++;
			}
			else 
			{
				while (xp_isdigit(ch)) 
				{
					ADDC (str, ch);
					ch = *fp++;
				}
			}
		}

		/* modifier */
		for (modifier = 0;;) 
		{
			if (ch == XP_CHAR('h')) modifier = MOD_SHORT;
			else if (ch == XP_CHAR('l')) 
			{
				modifier = (modifier == MOD_LONG)? MOD_LONGLONG: MOD_LONG;
			}
			else break;
			ch = *fp++;
		}		


		/* type */
		if (ch == XP_CHAR('%')) ADDC (str, ch);
		else if (ch == XP_CHAR('c') || ch == XP_CHAR('s')) 
		{
#if !defined(XP_CHAR_IS_MCHAR) && !defined(_WIN32)
			ADDC (str, 'l');
#endif
			ADDC (str, ch);
		}
		else if (ch == XP_CHAR('C') || ch == XP_CHAR('S')) 
		{
#ifdef _WIN32
			ADDC (str, ch);
#else
	#ifdef XP_CHAR_IS_MCHAR
			ADDC (str, 'l');
	#endif
			ADDC (str, xp_tolower(ch));
#endif
		}
		else if (ch == XP_CHAR('d') || ch == XP_CHAR('i') || 
		         ch == XP_CHAR('o') || ch == XP_CHAR('u') || 
		         ch == XP_CHAR('x') || ch == XP_CHAR('X')) 
		{
			if (modifier == MOD_SHORT) 
			{
				ADDC (str, 'h');
			}
			else if (modifier == MOD_LONG) 
			{
				ADDC (str, 'l');
			}
			else if (modifier == MOD_LONGLONG) 
			{
#if defined(_WIN32) && !defined(__LCC__)
				ADDC (str, 'I');
				ADDC (str, '6');
				ADDC (str, '4');
#else
				ADDC (str, 'l');
				ADDC (str, 'l');
#endif
			}
			ADDC (str, ch);
		}
		else if (ch == XP_CHAR('\0')) break;
		else ADDC (str, ch);
	}

	return xp_str_cield (&str);
}

